package cn.jingyuan.bee.utils.bean;

import cn.jingyuan.bee.utils.*;
import cn.jingyuan.bee.utils.bean.copier.BeanCopier;
import cn.jingyuan.bee.utils.bean.copier.CopyOptions;
import cn.jingyuan.bee.utils.bean.copier.ValueProvider;
import cn.jingyuan.bee.utils.collection.CollectionUtils;
import cn.jingyuan.bee.utils.convert.Convert;
import cn.jingyuan.bee.utils.lang.Editor;
import cn.jingyuan.bee.utils.lang.Filter;
import cn.jingyuan.bee.utils.map.CaseInsensitiveMap;
import cn.jingyuan.bee.utils.map.MapUtils;

import java.beans.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

/**
 * Bean 工具类<br>
 * 把一个拥有对属性进行 set 和 get 方法的类，我们就可以称之为 JavaBean。
 */
public class BeanUtils {

    /**
     * 判断是否为 Bean 对象，判定方法是：
     *
     * <pre>
     *     1、是否存在只有一个参数的 setXXX 方法
     *     2、是否存在 public 类型的字段
     * </pre>
     *
     * @param clazz 待测试类
     *
     * @return 是否为 Bean 对象
     *
     * @see #hasSetter(Class)
     */
    public static boolean isBean(Class<?> clazz) {
        return hasSetter(clazz) || hasPublicField(clazz);
    }

    /**
     * 判断是否有 Setter 方法<br>
     * 判定方法是是否存在只有一个参数的 setXXX 方法
     *
     * @param clazz 待测试类
     *
     * @return 是否为 Bean 对象
     */
    public static boolean hasSetter(Class<?> clazz) {
        if (ClassUtils.isNormalClass(clazz)) {
            final Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) {
                    // 检测包含标准的 setXXX 方法即视为标准的 JavaBean
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 判断是否为 Bean 对象<br>
     * 判定方法是是否存在只有一个参数的 setXXX 方法
     *
     * @param clazz 待测试类
     *
     * @return 是否为 Bean 对象
     */
    public static boolean hasGetter(Class<?> clazz) {
        if (ClassUtils.isNormalClass(clazz)) {
            for (Method method : clazz.getMethods()) {
                if (method.getParameterTypes().length == 0) {
                    if (method.getName().startsWith("get") || method.getName().startsWith("is")) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * 指定类中是否有 public 类型字段(static 字段除外)
     *
     * @param clazz 待测试类
     *
     * @return 是否有 public 类型字段
     */
    public static boolean hasPublicField(Class<?> clazz) {
        if (ClassUtils.isNormalClass(clazz)) {
            for (Field field : clazz.getFields()) {
                if (ModifierUtils.isPublic(field) && !ModifierUtils.isStatic(field)) {
                    // 非 static 的 public 字段
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 创建动态 Bean
     *
     * @param bean 普通 Bean 或 Map
     *
     * @return {@link DynamicBean}
     */
    public static DynamicBean createDynaBean(Object bean) {
        return new DynamicBean(bean);
    }

    /**
     * 查找类型转换器 {@link PropertyEditor}
     *
     * @param type 需要转换的目标类型
     *
     * @return {@link PropertyEditor}
     */
    public static PropertyEditor findEditor(Class<?> type) {
        return PropertyEditorManager.findEditor(type);
    }

    /**
     * 获取 {@link BeanDesc} Bean 描述信息
     *
     * @param clazz Bean 类
     *
     * @return {@link BeanDesc}
     */
    public static BeanDesc getBeanDesc(Class<?> clazz) {
        BeanDesc beanDesc = BeanDescCache.INSTANCE.getBeanDesc(clazz);
        if (null == beanDesc) {
            beanDesc = new BeanDesc(clazz);
            BeanDescCache.INSTANCE.putBeanDesc(clazz, beanDesc);
        }
        return beanDesc;
    }

    /**
     * 获得 Bean 字段描述数组
     *
     * @param clazz Bean 类
     *
     * @return 字段描述数组
     *
     * @throws BeanException 获取属性异常
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeanException {
        BeanInfo beanInfo;
        try {
            beanInfo = Introspector.getBeanInfo(clazz);
        } catch (IntrospectionException e) {
            throw new BeanException(e);
        }
        return ArrayUtils.filter(beanInfo.getPropertyDescriptors(), (Filter<PropertyDescriptor>) t -> {
            // 过滤掉 getClass 方法
            return !"class".equals(t.getName());
        });
    }

    /**
     * 获得字段名和字段描述 Map，获得的结果会缓存在 {@link BeanInfoCache} 中
     *
     * @param clazz Bean 类
     * @param ignoreCase 是否忽略大小写
     *
     * @return 字段名和字段描述 Map
     *
     * @throws BeanException 获取属性异常
     */
    public static Map<String, PropertyDescriptor> getPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase) throws BeanException {
        Map<String, PropertyDescriptor> map = BeanInfoCache.INSTANCE.getPropertyDescriptorMap(clazz, ignoreCase);
        if (null == map) {
            map = internalGetPropertyDescriptorMap(clazz, ignoreCase);
            BeanInfoCache.INSTANCE.putPropertyDescriptorMap(clazz, map, ignoreCase);
        }
        return map;
    }

    /**
     * 获得字段名和字段描述 Map。内部使用，直接获取 Bean 类的 PropertyDescriptor
     *
     * @param clazz Bean 类
     * @param ignoreCase 是否忽略大小写
     *
     * @return 字段名和字段描述 Map
     *
     * @throws BeanException 获取属性异常
     */
    private static Map<String, PropertyDescriptor> internalGetPropertyDescriptorMap(Class<?> clazz, boolean ignoreCase) throws BeanException {
        final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
        final Map<String, PropertyDescriptor> map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1)
                : new HashMap<>((int) (propertyDescriptors.length), 1);

        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            map.put(propertyDescriptor.getName(), propertyDescriptor);
        }
        return map;
    }

    /**
     * 获得 Bean 类属性描述，大小写敏感
     *
     * @param clazz Bean 类
     * @param fieldName 字段名
     *
     * @return PropertyDescriptor
     *
     * @throws BeanException 获取属性异常
     */
    public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, final String fieldName) throws BeanException {
        return getPropertyDescriptor(clazz, fieldName, false);
    }

    /**
     * 获得 Bean 类属性描述
     *
     * @param clazz Bean 类
     * @param fieldName 字段名
     * @param ignoreCase 是否忽略大小写
     *
     * @return PropertyDescriptor
     *
     * @throws BeanException 获取属性异常
     */
    public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, final String fieldName, boolean ignoreCase) throws BeanException {
        final Map<String, PropertyDescriptor> map = getPropertyDescriptorMap(clazz, ignoreCase);
        return (null == map) ? null : map.get(fieldName);
    }

    /**
     * 获得字段值，通过反射直接获得字段值，并不调用 getXXX 方法<br>
     * 对象同样支持 Map 类型，fieldNameOrIndex 即为 key
     *
     * @param bean Bean 对象
     * @param fieldNameOrIndex 字段名或序号，序号支持负数
     *
     * @return 字段值
     */
    public static Object getFieldValue(Object bean, String fieldNameOrIndex) {
        if (null == bean || null == fieldNameOrIndex) {
            return null;
        }

        if (bean instanceof Map) {
            return ((Map<?, ?>) bean).get(fieldNameOrIndex);
        } else if (bean instanceof Collection) {
            return CollectionUtils.get((Collection<?>) bean, Integer.parseInt(fieldNameOrIndex));
        } else if (ArrayUtils.isArray(bean)) {
            return ArrayUtils.get(bean, Integer.parseInt(fieldNameOrIndex));
        } else {// 普通 Bean 对象
            return ReflectUtils.getFieldValue(bean, fieldNameOrIndex);
        }
    }

    /**
     * 设置字段值，，通过反射设置字段值，并不调用 setXXX 方法<br>
     * 对象同样支持 Map 类型，fieldNameOrIndex 即为 key
     *
     * @param bean Bean
     * @param fieldNameOrIndex 字段名或序号，序号支持负数
     * @param value 值
     */

    public static void setFieldValue(Object bean, String fieldNameOrIndex, Object value) {
        if (bean instanceof Map) {
            ((Map) bean).put(fieldNameOrIndex, value);
        } else if (bean instanceof List) {
            CollectionUtils.setOrAppend((List) bean, Convert.toInt(fieldNameOrIndex), value);
        } else if (ArrayUtils.isArray(bean)) {
            ArrayUtils.setOrAppend(bean, Convert.toInt(fieldNameOrIndex), value);
        } else {
            // 普通 Bean 对象
            ReflectUtils.setFieldValue(bean, fieldNameOrIndex, value);
        }
    }

    /**
     * 解析 Bean 中的属性值
     *
     * @param <T> 属性值类型
     * @param bean Bean 对象，支持 Map、List、Collection、Array
     * @param expression 表达式，例如：person.friend[5].name
     *
     * @return Bean 属性值
     *
     * @see BeanPath#get(Object)
     */

    public static <T> T getProperty(Object bean, String expression) {
        return (T) BeanPath.create(expression).get(bean);
    }

    /**
     * 解析 Bean 中的属性值
     *
     * @param bean Bean 对象，支持 Map、List、Collection、Array
     * @param expression 表达式，例如：person.friend[5].name
     * @param value 属性值
     *
     * @see BeanPath#get(Object)
     */
    public static void setProperty(Object bean, String expression, Object value) {
        BeanPath.create(expression).set(bean, value);
    }

    /**
     * Map 转换为 Bean 对象
     *
     * @param <T> Bean 类型
     * @param map {@link Map}
     * @param beanClass Bean Class
     * @param isIgnoreError 是否忽略注入错误
     *
     * @return Bean
     */
    public static <T> T mapToBean(Map<?, ?> map, Class<T> beanClass, boolean isIgnoreError) {
        return fillBeanWithMap(map, ReflectUtils.newInstance(beanClass), isIgnoreError);
    }

    /**
     * Map 转换为 Bean 对象<br>
     * 忽略大小写
     *
     * @param <T> Bean 类型
     * @param map Map
     * @param beanClass Bean Class
     * @param isIgnoreError 是否忽略注入错误
     *
     * @return Bean
     */
    public static <T> T mapToBeanIgnoreCase(Map<?, ?> map, Class<T> beanClass, boolean isIgnoreError) {
        return fillBeanWithMapIgnoreCase(map, ReflectUtils.newInstance(beanClass), isIgnoreError);
    }

    /**
     * Map 转换为 Bean 对象
     *
     * @param <T> Bean 类型
     * @param map {@link Map}
     * @param beanClass Bean Class
     * @param copyOptions 转 Bean 选项
     *
     * @return Bean
     */
    public static <T> T mapToBean(Map<?, ?> map, Class<T> beanClass, CopyOptions copyOptions) {
        return fillBeanWithMap(map, ReflectUtils.newInstance(beanClass), copyOptions);
    }

    /**
     * 使用 Map 填充 Bean 对象
     *
     * @param <T> Bean 类型
     * @param map Map
     * @param bean Bean
     * @param isIgnoreError 是否忽略注入错误
     *
     * @return Bean
     */
    public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isIgnoreError) {
        return fillBeanWithMap(map, bean, false, isIgnoreError);
    }

    /**
     * 使用 Map 填充 Bean 对象，可配置将下划线转换为驼峰
     *
     * @param <T> Bean 类型
     * @param map Map
     * @param bean Bean
     * @param isToCamelCase 是否将下划线模式转换为驼峰模式
     * @param isIgnoreError 是否忽略注入错误
     *
     * @return Bean
     */
    public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isToCamelCase, boolean isIgnoreError) {
        return fillBeanWithMap(map, bean, isToCamelCase, CopyOptions.create().setIgnoreError(isIgnoreError));
    }

    /**
     * 使用 Map 填充 Bean 对象，忽略大小写
     *
     * @param <T> Bean 类型
     * @param map Map
     * @param bean Bean
     * @param isIgnoreError 是否忽略注入错误
     *
     * @return Bean
     */
    public static <T> T fillBeanWithMapIgnoreCase(Map<?, ?> map, T bean, boolean isIgnoreError) {
        return fillBeanWithMap(map, bean, CopyOptions.create().setIgnoreCase(true).setIgnoreError(isIgnoreError));
    }

    /**
     * 使用 Map 填充 Bean 对象
     *
     * @param <T> Bean 类型
     * @param map Map
     * @param bean Bean
     * @param copyOptions 属性复制选项 {@link CopyOptions}
     *
     * @return Bean
     */
    public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, CopyOptions copyOptions) {
        return fillBeanWithMap(map, bean, false, copyOptions);
    }

    /**
     * 使用 Map 填充 Bean 对象
     *
     * @param <T> Bean 类型
     * @param map Map
     * @param bean Bean
     * @param isToCamelCase 是否将 Map 中的下划线风格 key 转换为驼峰风格
     * @param copyOptions 属性复制选项 {@link CopyOptions}
     *
     * @return Bean
     */
    public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isToCamelCase, CopyOptions copyOptions) {
        if (MapUtils.isEmpty(map)) {
            return bean;
        }
        if (isToCamelCase) {
            map = MapUtils.toCamelCaseMap(map);
        }
        return BeanCopier.create(map, bean, copyOptions).copy();
    }

    /**
     * 对象或 Map 转 Bean
     *
     * @param <T> 转换的 Bean 类型
     * @param source Bean 对象或 Map
     * @param clazz 目标的 Bean 类型
     *
     * @return Bean 对象
     */
    public static <T> T toBean(Object source, Class<T> clazz) {
        final T target = ReflectUtils.newInstance(clazz);
        copyProperties(source, target);
        return target;
    }

    /**
     * ServletRequest 参数转 Bean
     *
     * @param <T> Bean 类型
     * @param beanClass Bean Class
     * @param valueProvider 值提供者
     * @param copyOptions 拷贝选项，见 {@link CopyOptions}
     *
     * @return Bean
     */
    public static <T> T toBean(Class<T> beanClass, ValueProvider<String> valueProvider, CopyOptions copyOptions) {
        return fillBean(ReflectUtils.newInstance(beanClass), valueProvider, copyOptions);
    }

    /**
     * 填充 Bean 的核心方法
     *
     * @param <T> Bean 类型
     * @param bean Bean
     * @param valueProvider 值提供者
     * @param copyOptions 拷贝选项，见 {@link CopyOptions}
     *
     * @return Bean
     */
    public static <T> T fillBean(T bean, ValueProvider<String> valueProvider, CopyOptions copyOptions) {
        if (null == valueProvider) {
            return bean;
        }

        return BeanCopier.create(valueProvider, bean, copyOptions).copy();
    }

    /**
     * 对象转 Map，不进行驼峰转下划线，不忽略值为空的字段
     *
     * @param bean bean 对象
     *
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object bean) {
        return beanToMap(bean, false, false);
    }

    /**
     * 对象转 Map
     *
     * @param bean bean 对象
     * @param isToUnderlineCase 是否转换为下划线模式
     * @param ignoreNullValue 是否忽略值为空的字段
     *
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object bean, boolean isToUnderlineCase, boolean ignoreNullValue) {
        return beanToMap(bean, new LinkedHashMap<>(), isToUnderlineCase, ignoreNullValue);
    }

    /**
     * 对象转 Map
     *
     * @param bean bean 对象
     * @param targetMap 目标的 Map
     * @param isToUnderlineCase 是否转换为下划线模式
     * @param ignoreNullValue 是否忽略值为空的字段
     *
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object bean, Map<String, Object> targetMap, final boolean isToUnderlineCase, boolean ignoreNullValue) {
        if (bean == null) {
            return null;
        }

        return beanToMap(bean, targetMap, ignoreNullValue, key -> isToUnderlineCase ? StringUtils.toUnderlineCase(key) : key);
    }

    /**
     * 对象转 Map<br>
     * 通过实现 {@link Editor} 可以自定义字段值，如果这个 Editor 返回 null 则忽略这个字段，以便实现：
     *
     * <pre>
     * 1. 字段筛选，可以去除不需要的字段
     * 2. 字段变换，例如实现驼峰转下划线
     * 3. 自定义字段前缀或后缀等等
     * </pre>
     *
     * @param bean bean 对象
     * @param targetMap 目标的 Map
     * @param ignoreNullValue 是否忽略值为空的字段
     * @param keyEditor 属性字段（Map 的 key）编辑器，用于筛选、编辑 key
     *
     * @return Map
     */
    public static Map<String, Object> beanToMap(Object bean, Map<String, Object> targetMap, boolean ignoreNullValue, Editor<String> keyEditor) {
        if (bean == null) {
            return null;
        }

        final Collection<BeanDesc.PropDesc> props = BeanUtils.getBeanDesc(bean.getClass()).getProps();

        String key;
        Method getter;
        Object value;
        for (BeanDesc.PropDesc prop : props) {
            key = prop.getFieldName();
            // 过滤 class 属性
            // 得到 property 对应的 getter 方法
            getter = prop.getGetter();
            if (null != getter) {
                // 只读取有 getter 方法的属性
                try {
                    value = getter.invoke(bean);
                } catch (Exception ignore) {
                    continue;
                }
                if (!ignoreNullValue || (null != value && !value.equals(bean))) {
                    key = keyEditor.edit(key);
                    if (null != key) {
                        targetMap.put(key, value);
                    }
                }
            }
        }
        return targetMap;
    }

    /**
     * 复制 Bean 对象属性
     *
     * @param source 源 Bean 对象
     * @param target 目标 Bean 对象
     */
    public static void copyProperties(Object source, Object target) {
        copyProperties(source, target, CopyOptions.create());
    }

    /**
     * 复制 Bean 对象属性<br>
     * 限制类用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将 editable 设置为父类
     *
     * @param source 源 Bean 对象
     * @param target 目标 Bean 对象
     * @param ignoreProperties 不拷贝的的属性列表
     */
    public static void copyProperties(Object source, Object target, String... ignoreProperties) {
        copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties));
    }

    /**
     * 复制 Bean 对象属性<br>
     *
     * @param source 源 Bean 对象
     * @param target 目标 Bean 对象
     * @param ignoreCase 是否忽略大小写
     */
    public static void copyProperties(Object source, Object target, boolean ignoreCase) {
        BeanCopier.create(source, target, CopyOptions.create().setIgnoreCase(ignoreCase)).copy();
    }

    /**
     * 复制 Bean 对象属性<br>
     * 限制类用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将 editable 设置为父类
     *
     * @param source 源 Bean 对象
     * @param target 目标 Bean 对象
     * @param copyOptions 拷贝选项，见 {@link CopyOptions}
     */
    public static void copyProperties(Object source, Object target, CopyOptions copyOptions) {
        if (null == copyOptions) {
            copyOptions = new CopyOptions();
        }
        BeanCopier.create(source, target, copyOptions).copy();
    }

    /**
     * 给定的 Bean 的类名是否匹配指定类名字符串<br>
     * 如果 isSimple 为 {@code false}，则只匹配类名而忽略包名，例如：com.jk.husky.utils.TestEntity 只匹配 TestEntity<br>
     * 如果 isSimple 为 {@code true}，则匹配包括包名的全类名，例如：com.jk.husky.utils.TestEntity 匹配 com.jk.husky.utils.TestEntity
     *
     * @param bean Bean
     * @param beanClassName Bean 的类名
     * @param isSimple 是否只匹配类名而忽略包名，true 表示忽略包名
     *
     * @return 是否匹配
     */
    public static boolean isMatchName(Object bean, String beanClassName, boolean isSimple) {
        return ClassUtils.getClassName(bean, isSimple).equals(isSimple ? StringUtils.upperFirst(beanClassName) : beanClassName);
    }

    /**
     * 把 Bean 里面的 String 属性做 trim 操作。此方法直接对传入的 Bean 做修改<br>
     * 通常 bean 直接用来绑定页面的 input，用户的输入可能首尾存在空格，通常保存数据库前需要把首尾空格去掉
     *
     * @param <T> Bean 类型
     * @param bean Bean 对象
     * @param ignoreFields 不需要 trim 的 Field 名称列表（不区分大小写）
     *
     * @return 处理后的 Bean 对象
     */
    public static <T> T trimStrFields(T bean, String... ignoreFields) {
        if (bean == null) {
            return bean;
        }

        final Field[] fields = ReflectUtils.getFields(bean.getClass());
        for (Field field : fields) {
            if (ignoreFields != null && ArrayUtils.containsIgnoreCase(ignoreFields, field.getName())) {
                // 不处理忽略的 Fields
                continue;
            }
            if (String.class.equals(field.getType())) {
                // 只有 String 的 Field 才处理
                final String val = (String) ReflectUtils.getFieldValue(bean, field);
                if (null != val) {
                    final String trimVal = StringUtils.trim(val);
                    if (!val.equals(trimVal)) {
                        // Field Value 不为 null，且首尾有空格才处理
                        ReflectUtils.setFieldValue(bean, field, trimVal);
                    }
                }
            }
        }

        return bean;
    }

    /**
     * 判断 Bean 是否为非空对象，非空对象表示本身不为<code>null</code>或者含有非<code>null</code>属性的对象
     *
     * @param bean Bean 对象
     * @param ignoreFiledNames 忽略检查的字段名
     *
     * @return 是否为空，<code>true</code> - 空 / <code>false</code> - 非空
     */
    public static boolean isNotEmpty(Object bean, String... ignoreFiledNames) {
        return !isEmpty(bean, ignoreFiledNames);
    }

    /**
     * 判断 Bean 是否为空对象，空对象表示本身为<code>null</code>或者所有属性都为 <code>null</code>
     *
     * @param bean Bean 对象
     * @param ignoreFiledNames 忽略检查的字段名
     *
     * @return 是否为空，<code>true</code> - 空 / <code>false</code> - 非空
     */
    public static boolean isEmpty(Object bean, String... ignoreFiledNames) {
        if (null != bean) {
            for (Field field : ReflectUtils.getFields(bean.getClass())) {
                if ((!ArrayUtils.contains(ignoreFiledNames, field.getName()))
                        && null != ReflectUtils.getFieldValue(bean, field)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * 判断 Bean 是否包含值为<code>null</code>的属性<br>
     * 对象本身为 <code>null</code> 也返回 true
     *
     * @param bean Bean 对象
     * @param ignoreFiledNames 忽略检查的字段名
     *
     * @return 是否包含值为 <code>null</code> 的属性，<code>true</code> - 包含 / <code>false</code> - 不包含
     */
    public static boolean hasNullField(Object bean, String... ignoreFiledNames) {
        if (null == bean) {
            return true;
        }
        for (Field field : ReflectUtils.getFields(bean.getClass())) {
            if ((!ArrayUtils.contains(ignoreFiledNames, field.getName())) && null == ReflectUtils.getFieldValue(bean, field)) {
                return true;
            }
        }
        return false;
    }

}
